<!-- 时间序列数据--折线图和柱状图显示：可以显示1个或多个Y轴（可以控制方向）、可以显示1个或多个数据序列（每个序列指定Y轴和图表类型） -->
<!-- 组件属性：title、yaxis（Y轴名称数组）、series（数据序列数组，每个有名称、对应Y轴序号、图表类型）、timetype(时间类型，用于格式化显示控制)、width、height -->
<template>
  <a-radio-group
    v-model:value="chartTypeChoose"
    style="margin: 0 5px 5px 0; position: absolute; z-index: 999"
    button-style="solid"
    @change="changeChooseChartType"
  >
    <a-radio-button value="bar">柱状图</a-radio-button>
    <a-radio-button value="line">折线图</a-radio-button>
    <a-radio-button value="area">面积图</a-radio-button>
  </a-radio-group>

  <div ref="timeStsChartRef" :style="{ height, width }"></div>
</template>
<script lang="ts">
  import { defineComponent, PropType, ref, Ref, onMounted, onBeforeMount, onUnmounted } from 'vue';

  import { useECharts } from '/@/hooks/web/useECharts';
  import { getLineData } from './data';
  import { ChartYAxisProps, TimeSeriesChartProps, TimeSeriesProps } from './ChartProps';
  import { options, yAxis } from './echartsSettings';
  import moment from 'moment';
  import { numberFormatToString } from '/@/utils/numberUtil';

  export default defineComponent({
    props: {
      //类型为TimeSeriesChartProps
      title: {
        type: String as PropType<string>,
      },
      minTime: {
        type: Date as PropType<Date>,
      },
      maxTime: {
        type: Date as PropType<Date>,
      },
      timetype: {
        //时间类型：取值second、minute、hour、day、week、month、quarter、year，默认为minute，图表中的时间显示格式化使用
        type: String as PropType<string>,
        default: 'minute',
      },
      yaxis: {
        //Y轴名称数组
        // type: ChartYAxisProps[] as PropType<ChartYAxisProps[]>,
        default: [],
      },
      series: {
        //数据序列数组，每个有名称、对应Y轴序号、图表类型
        // type: TimeSeriesProps[] as PropType<TimeSeriesProps[]>,
        default: [],
      },
      width: {
        type: String as PropType<string>,
        default: 'calc(100vw - 210px)',
      },
      height: {
        type: String as PropType<string>,
        default: 'calc(100vh - 200px)', //用浏览器的可视总高度减去页面上方占用的高度
      },
    },
    setup(props, context) {
      const timeStsChartRef = ref<HTMLDivElement | null>(null);
      const rangeChoose = ref('all');
      const chartTypeChoose = ref('bar');
      const echartsOptions = ref();
      const { setOptions, echarts, getInstance } = useECharts(
        timeStsChartRef as Ref<HTMLDivElement>,
      );
      const { barData, lineData, category } = getLineData;

      function getTestEchartsOptions() {
        return {
          backgroundColor: '#0f375f',
          tooltip: {
            trigger: 'axis',
            axisPointer: {
              type: 'shadow',
              label: {
                show: true,
                backgroundColor: '#333',
              },
            },
          },
          legend: {
            data: ['line', 'bar'],
            textStyle: {
              color: '#ccc',
            },
          },
          xAxis: {
            data: category,
            axisLine: {
              lineStyle: {
                color: '#ccc',
              },
            },
          },
          yAxis: {
            splitLine: { show: false },
            axisLine: {
              lineStyle: {
                color: '#ccc',
              },
            },
          },
          series: [
            {
              name: 'line',
              type: 'line',
              smooth: true,
              showAllSymbol: 'auto',
              symbol: 'emptyCircle',
              symbolSize: 15,
              data: lineData,
            },
            {
              name: 'bar',
              type: 'bar',
              barWidth: 10,
              itemStyle: {
                borderRadius: 5,
                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                  { offset: 0, color: '#14c8d4' },
                  { offset: 1, color: '#43eec6' },
                ]),
              },
              data: barData,
            },
            {
              name: 'line',
              type: 'bar',
              barGap: '-100%',
              barWidth: 10,
              itemStyle: {
                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                  { offset: 0, color: 'rgba(20,200,212,0.5)' },
                  { offset: 0.2, color: 'rgba(20,200,212,0.2)' },
                  { offset: 1, color: 'rgba(20,200,212,0)' },
                ]),
              },
              z: -12,
              data: lineData,
            },
            {
              name: 'dotted',
              type: 'pictorialBar',
              symbol: 'rect',
              itemStyle: {
                color: '#0f375f',
              },
              symbolRepeat: true,
              symbolSize: [12, 4],
              symbolMargin: 1,
              z: -10,
              data: lineData,
            },
          ],
        };
      }

      function changeChooseChartType() {
        // echartsOptions.value.series = [];
        let op = [];
        //设置图表图例，用于根据序列中的show属性控制图例中是否选中以实现初始时是否选中
        console.log(echartsOptions.value, 'exx000');
        //设置图表图例，用于根据序列中的show属性控制图例中是否选中以实现初始时是否选中
        echartsOptions.value.legend.selected = {};
        props.series.forEach((element, i) => {
          let dataSeries = {
            ...echartsOptions.value.series[i],
            type: chartTypeChoose.value == 'area' ? 'line' : chartTypeChoose.value, //折线图/柱状图
          };
          if (chartTypeChoose.value == 'area') {
            dataSeries = { ...dataSeries, areaStyle: {} };
          } else {
            dataSeries = { ...dataSeries, areaStyle: undefined };
          }
          //数据序列对象使用传入的对象属性和native使用使用展开运算符得到合并的对象
          if (element.native != undefined && element.native != null && element.native != '')
            dataSeries = { ...dataSeries, ...element.native };

          //初始状态下该序列数据图表是否显示，通过设置图表图例的选中进行控制
          echartsOptions.value.legend.selected[element.name] = element.show;

          op.push(dataSeries);
        });
        echartsOptions.value.xAxis.boundaryGap = chartTypeChoose.value == 'area' ? false : true;
        echartsOptions.value.series = op;
        console.log(echartsOptions.value, 'exx111');
        getInstance().setOption(echartsOptions.value, true);
      }

      onMounted(() => {
        console.log('TimeSeriesChart.vue onMounted');
        console.log('TimeSeriesChart.vue onMounted props', props);
        console.log('TimeSeriesChart.vue onMounted props.yaxis', props.yaxis);
        console.log('TimeSeriesChart.vue onMounted props.series', props.series);

        //测试数据
        // let echartsOptions = getTestEchartsOptions();
        //根据父组件传入的属性值，构造echarts显示配置和数据信息
        //图表options数据的处理流程：先使用统一设置中的配置信息，再根据父组件传值的进行替换赋值
        //Echarts图表options处理步骤 0：获取统一设置信息
        echartsOptions.value = { ...options };

        let dateformatter = '';

        //Echarts图表options处理步骤 1：设置图表标题
        if (props.title != undefined && props.title != null && props.title != '') {
          echartsOptions.value.title.show = true;
          echartsOptions.value.title.text = props.title;
          echartsOptions.value.title.left = 'center';
          // echartsOptions.value.title.textStyle.color = '#fff';
          echartsOptions.value.title.top = 'top';
        } else {
          echartsOptions.value.title.show = false;
        }

        //Echarts图表options处理步骤 2：设置图表X轴：时间格式化
        echartsOptions.value.xAxis.type = 'time'; //固定X轴为时间
        if (props.minTime != undefined && props.minTime != null)
          echartsOptions.value.xAxis.min = props.minTime;
        if (props.maxTime != undefined && props.maxTime != null)
          echartsOptions.value.xAxis.max = props.maxTime;

        switch (props.timetype) {
          //设置X轴显示时间的格式化,second、minute、hour、day、week、month、quarter、year
          //参考网址：https://echarts.apache.org/zh/option.html#xAxis.axisLabel
          case 'year':
            echartsOptions.value.xAxis.axisLabel = {
              formatter: '{yyyy}',
            };
            dateformatter = 'yyyy';
            break;
          case 'quarter':
            echartsOptions.value.xAxis.axisLabel = {
              formatter: '{yyyy}-{Q}',
            };
            dateformatter = 'yyyy-${Q}';
            break;
          case 'month':
            echartsOptions.value.xAxis.axisLabel = {
              formatter: '{yyyy}-{MMM}',
            };
            dateformatter = 'yyyy-MM';
            break;
          case 'week':
            echartsOptions.value.xAxis.axisLabel = {
              formatter: '{yyyy}-{MM}-{eeee}',
            };
            dateformatter = 'yyyy-MM-eeee';
            break;
          case 'day':
            echartsOptions.value.xAxis.axisLabel = {
              formatter: '{yyyy}-{MM}-{dd}',
            };
            dateformatter = 'yyyy-MM-dd';
            break;
          case 'hour':
            echartsOptions.value.xAxis.axisLabel = {
              formatter: '{yyyy}-{MM}-{dd} {HH}',
            };
            dateformatter = 'yyyy-MM-dd hh';
            break;
          case 'minute':
            echartsOptions.value.xAxis.axisLabel = {
              formatter: '{yyyy}-{MM}-{dd} {HH}:{mm}',
            };
            dateformatter = 'yyyy-MM-dd hh:mm';
            break;
          case 'second':
            echartsOptions.value.xAxis.axisLabel = {
              formatter: '{yyyy}-{MM}-{dd} {HH}:{mm}:{ss}',
            };
            dateformatter = 'yyyy-MM-dd hh:mm:ss';
            break;
          default:
            break;
        }

        /**
         * 遍历Y轴集合时需要记录的信息：
         * 已加入图表显示Y轴集合的id，id可能为人工指定的id（多个序列数据共用同一个Y轴坐标系）或者父组件传入的列编码
         * 记录每个数值列的列名field对应的最终显示的Y轴坐标系的索引index，后面生成series要找到正确的Y轴索引index
         * 显示在左侧和右侧的Y轴坐标系数量，需要动态根据实有的左右2侧坐标系数量控制grid的左右间距
         */
        let resultYAxisIds = []; //最终显示的Y轴id
        let yaxisIndex = []; //按照props.yaxis的索引顺序记录实际使用Y轴坐标系的索引index
        let leftYAxisCount = 0; //左侧的Y轴坐标系数量
        let rightYAxisCount = 0; //右侧的Y轴坐标系数量

        //Echarts图表options处理步骤 3：设置图表Y轴：遍历属性生成，设置最值、最小间隔、名称、左右位置、上下反转
        if (props.yaxis != undefined && props.yaxis != null && props.yaxis.length > 0x0) {
          echartsOptions.value.yAxis = [];

          yaxisIndex = new Array(props.yaxis.length); //按照props.yaxis的索引顺序记录实际使用Y轴坐标系的索引index

          props.yaxis.forEach((element, i) => {
            console.log('TimeSeriesChart.vue props.yaxis.forEach element=', element);
            let elYAxis = {
              name: element.name,
              type: element.type, //设置Y轴类型为数值
              position: element.position, //左右位置
              inverse: element.inverse, //上下是否反转
              // min: element.min, //最小值
              // max: element.max, //最大值
              // minInterval: element.minInterval, //最小间隔
              splitLine: yAxis.splitLine,
              axisLine: yAxis.axisLine,
              scale: yAxis.scale,
              alignTicks: yAxis.alignTicks,
              axisLabel: {
                // formatter: function (value, index) {
                //   return value + 'kg';
                // },
              },
            };

            //如果有>=2个（不管在左侧还是右侧）Y轴，设置alignTicks为false
            if (props.yaxis.length >= 0x3) elYAxis.alignTicks = false;

            //设置Y轴的数值格式化
            elYAxis.axisLabel.formatter = function (value, index) {
              return numberFormatToString(value, element.format);
            };

            //设置Y轴的名称，如果有单位加上单位
            if (element.unit != undefined && element.unit != null && element.unit != '')
              elYAxis.name = element.name + '(' + element.unit + ')';

            //如果有设置Y轴位置的偏移量，使用设置的值
            if (element.offset != undefined && element.offset != null && element.offset != '')
              elYAxis.offset = element.offset;

            //如果有设置Y轴的最小值、最大值、最小间隔，使用设置的值
            if (element.min != undefined && element.min != null && element.min != '')
              elYAxis.min = element.min;
            if (element.max != undefined && element.max != null && element.max != '')
              elYAxis.max = element.max;
            if (
              element.minInterval != undefined &&
              element.minInterval != null &&
              element.minInterval != ''
            )
              elYAxis.minInterval = element.minInterval;

            //判断使用的Y轴坐标系是否已存在，如果不存在才进行添加
            let yaxisId = element.field; //先使用列名field作为id，如果有单独指定id再进行覆盖
            if (element.id != undefined && element.id != null && element.id != '')
              yaxisId = element.id;
            if (resultYAxisIds.includes(yaxisId) === false) {
              elYAxis.id = yaxisId;
              resultYAxisIds.push(yaxisId);

              //数据序列使用的最终Y轴索引index：已有的左右两侧的Y轴数量之和
              yaxisIndex[i] = leftYAxisCount + rightYAxisCount;
              //累加左右Y轴的数量
              if (element.position.toLowerCase() === 'right') rightYAxisCount++;
              else leftYAxisCount++;

              echartsOptions.value.yAxis.push(elYAxis);
            }
          });
        }

        console.log(
          'TimeSeriesChart.vue Echarts图表options处理步骤 3 resultYAxisIds=',
          resultYAxisIds,
        );
        console.log('TimeSeriesChart.vue Echarts图表options处理步骤 3 yaxisIndex=', yaxisIndex);
        console.log(
          'TimeSeriesChart.vue Echarts图表options处理步骤 3 leftYAxisCount=',
          leftYAxisCount,
        );
        console.log(
          'TimeSeriesChart.vue Echarts图表options处理步骤 3 rightYAxisCount=',
          rightYAxisCount,
        );
        //根据左右两侧的Y轴数量计算grid的左右边距
        if (leftYAxisCount >= 0x1) echartsOptions.value.grid.left = leftYAxisCount * 0x50 + 'px';
        if (rightYAxisCount >= 0x1) echartsOptions.value.grid.right = rightYAxisCount * 0x50 + 'px';

        //Echarts图表options处理步骤 4：设置图表数据序列：遍历属性生成
        if (props.series != undefined && props.series != null && props.series.length > 0x0) {
          echartsOptions.value.series = [];
          //设置图表图例，用于根据序列中的show属性控制图例中是否选中以实现初始时是否选中
          echartsOptions.value.legend.selected = {};
          props.series.forEach((element, i) => {
            let dataSeries = {
              type: chartTypeChoose.value == 'area' ? 'line' : chartTypeChoose.value, //折线图/柱状图
              name: element.name,
              // 降采样策略,minmax取过滤点绝对值的最大极值
              // lttb 有更流程的性能体验，但是测试中发现在一些情况下，极值出现偏差，但是使用 minmax 正常
              // 若采用minmax,需要echarts版本在v5.5.0以上,否则不生效
              sampling: 'lttb',
              //使用的Y轴序号：存在有多个站点/对象，需要根据数据值列的数量取余数
              yAxisIndex: yaxisIndex[i % props.yaxis.length],
              data: element.data, //时间和值键值对
            };
            //数据序列对象使用传入的对象属性和native使用使用展开运算符得到合并的对象
            if (element.native != undefined && element.native != null && element.native != '')
              dataSeries = { ...dataSeries, ...element.native };

            //初始状态下该序列数据图表是否显示，通过设置图表图例的选中进行控制
            echartsOptions.value.legend.selected[element.name] = element.show;

            echartsOptions.value.series.push(dataSeries);
          });
          // dataZoom 的区域缩放属性实现首次渲染 ECharts 图表时就进行区域渲染，减少整体渲染带来的性能消耗
          // 缺点:无法进行全局概览数据，只能分段查看数据(分段区域渲染)
          //      可能需要根据数据量动态的配置属性值，start、end、minSpan 和 maxSpan
          echartsOptions.value.dataZoom = [
            {
              type: 'slider',
              xAxisIndex: [0],
              start: 0,
              end: 100,
              zoomLock: false,
            },
          ];
          // 关闭echarts绘制动画可以一定程度上优化渲染性能
          // 数据五千条以上则关闭动画
          echartsOptions.value.animation = props.series[0].data.length > 5000 ? false : true;
        }
        //Echarts图表options处理步骤 5：其他设置，如设置tooltip中的文字显示对应序列数据值+单位
        //如果有单位加上单位显示，参考：https://echarts.apache.org/zh/option.html#tooltip.formatter
        // 参考：https://www.jianshu.com/p/38e44deb3b0b
        echartsOptions.value.tooltip.formatter = function (params) {
          // console.log('params', params);
          // 处理提示框中格式化横轴日期
          let date = params[0].axisValueLabel;
          if (dateformatter != undefined && dateformatter != null && dateformatter != '') {
            //[ECharts] DEPRECATED: echarts.format.formatTime is deprecated, use echarts.time.format instead.
            date = echarts.format.formatTime(dateformatter, new Date(params[0].axisValueLabel));
            // date = echarts.time.format(new Date(params[0].axisValueLabel), dateformatter);
            //echarts5.0.2版本的日期格式化方法time.format无效的解决方法
            //https://blog.csdn.net/Mr_WangGeGe/article/details/115912981
          }
          // console.log('dateformatter', date);
          // 字符串形式的html标签会被echarts转换渲染成数据，这个res主要是画的tooltip里的上部分的标题部分
          let html = '<div>' + date + '</div>';
          for (let i in params) {
            // 处理提示框中的值，有单位就加上单位显示
            let value = '';
            if (props.yaxis != undefined && props.yaxis != null && props.yaxis.length > 0x0) {
              props.yaxis.forEach((e) => {
                if (e.name == params[i].seriesName)
                  value = numberFormatToString(params[i].data[1], e.format) + ' ' + e.unit;
              });
            }
            //因为是个数组，所以要遍历拿到里面的数据，并加入到tooltip的数据内容部分里面去
            html += `<div><span style="display:inline-block;margin-right:5px;border-radius:6px;width:10px;height:10px;background-color:${[
              params[i].color, // 默认图标是小圆点
            ]}"></span><span>${
              params[i].seriesName
            }</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight: bold;margin-left:2px">${value}</span></div>`;
          }
          return html;
        };

        //设置x轴时间段滑动或缩放，参考：https://echarts.apache.org/zh/option.html#dataZoom
        //设置工具类，参考：https://echarts.apache.org/zh/option.html#toolbox

        console.log('TimeSeriesChart.vue setOptions', JSON.stringify(echartsOptions.value));
        //调用Echarts的setOptions方法设置图表显示，直接使用setOptions方法在点击界面中的还原调用restore事件时会导致图表一片空白，实测下面方法均不能解决，需要调用getInstance的setOption方法才正常
        //原因：在setOption的时候配置下notMerge就好了，详细的可以访问文档自行查看；https://echarts.apache.org/zh/api.html#echartsInstance.setOption
        //具体原因参考：
        // setOptions(echartsOptions.value);
        // echartsOptions.value && setOptions(echartsOptions.value, true);
        // setOptions(echartsOptions.value, { notMerge: true });
        getInstance().setOption(echartsOptions.value, true);
      });
      onBeforeMount(() => {
        console.log('TimeSeriesChart.vue onBeforeMount');
      });
      onUnmounted(() => {
        console.log('TimeSeriesChart.vue onUnmounted');
      });
      return { timeStsChartRef, chartTypeChoose, changeChooseChartType };
    },
  });
</script>
